/*
 * @Author: yuht
 * @Date: 2022-11-28 13:39:33
 * @LastEditors: tianranjuan 971502799@qq.com
 * @LastEditTime: 2022-11-28 17:58:07
 * @Description: 基于Promise的IndexDB封装，应用于需要前端本地存储的场景
 */

const indexedDB =
  window.indexedDB ||
  window.mozIndexedDB ||
  window.webkitIndexedDB ||
  window.msIndexedDB;

class LocalDB {
  static get support() {
    return !!indexedDB;
  }

  static get EVENT_TYPES() {
    return {
      // 数据库打开失败
      ON_OPEN_ERROR: 0,
      // 数据库打开成功
      ON_OPEN_SUCCESS: 1,
      // 数据库更新
      ON_OPEN_UPGRADE: 2,
      // 表创建成功
      ON_TABLE_CTREA_SUCCESS: 10,
      // 创建表已存在
      ON_TABLE_EXIST: 11,
      // 创建列时，KEY值不存在
      ON_TABLE_CREATE_COLUMN_KEY_NOT_EXIST: 12,
      // 单条数据插入成功
      ON_INSTER_SUCCESS: 20,
      // 单条数据插入失败
      ON_INSTER_ERROR: 21,
    };
  }

  constructor() {
    this.db = null;
    this.dbName = "";
    this.version = 1;
  }

  /**
   * 打开数据库
   * @param {*} dbName 数据库名称
   * @param {*} version 数据库版本
   */
  open(dbName, version = 1, onupgradeneeded) {
    return new Promise((resolve, reject) => {
      const request = indexedDB.open(dbName);
      this.version = version;
      this.dbName = dbName;

      // 当数据库更新时
      request.onupgradeneeded = async (event) => {
        this.db = event.target.result;
        // 因为必须在onupgradeneeded的事物里才能创建表，所以需要传入一个钩子函数来触发
        onupgradeneeded && onupgradeneeded(this);
        resolve(
          this.getMessage(LocalDB.EVENT_TYPES.ON_OPEN_UPGRADE, "数据库更新成功")
        );
      };

      // 当数据库打开成功时
      request.onsuccess = (event) => {
        this.db = event.target.result;
        resolve(
          this.getMessage(LocalDB.EVENT_TYPES.ON_OPEN_SUCCESS, "数据库打开成功")
        );
      };

      // 当数据库打开失败时
      request.onerror = (event) => {
        reject(
          this.getMessage(
            LocalDB.EVENT_TYPES.ON_OPEN_ERROR,
            `数据库打开失败：${event.target.errorCode}`
          )
        );
      };
    });
  }

  /**
   * 创建表
   * @param {*} tableName
   * @param {*} keyPath
   * @param {*} autoIncrement
   * @returns
   */
  createTable(tableName, columns) {
    // 如果表明不存在就创建表，否则报错
    if (!this.hasTable(tableName)) {
      const params = {
        keyPath: "__id__",
        autoIncrement: true,
      };

      // 创建表
      const table = this.db.createObjectStore(tableName, params);
      // 创建列
      columns.forEach(({ key, unique = false }) => {
        if (!key) {
          return Promise.reject(
            this.getMessage(
              LocalDB.EVENT_TYPES.ON_TABLE_CREATE_COLUMN_KEY_NOT_EXIST,
              `表 ${tableName} 在创建列时发生错误，原因：缺少必要字段 'key' `
            )
          );
        } else {
          table.createIndex(key, key, {
            unique,
          });
        }
      });

      return Promise.resolve(
        this.getMessage(
          LocalDB.EVENT_TYPES.ON_TABLE_CTREA_SUCCESS,
          `表创建成功 ${table} `
        )
      );
    } else {
      return Promise.reject(
        this.getMessage(
          LocalDB.EVENT_TYPES.ON_TABLE_EXIST,
          `表 ${tableName} 创建失败，原因：表已存在`
        )
      );
    }
  }

  /**
   * 查询全部
   */
  query(tableName) {
    return new Promise((resolve, reject) => {
      const transaction = this.db.transaction([tableName], "readwrite");
      const table = transaction.objectStore(tableName);
      const getAll = table.getAll();
      getAll.onsuccess = (event) => {
        resolve(event.target.result);
      };

      getAll.onerror = (event) => {
        reject(
          this.getMessage(
            LocalDB.EVENT_TYPES.ON_INSTER_ERROR,
            `查询数据时发生错误: ${event.target.errorCode}`
          )
        );
      };
    });
  }

  insert(tableName, data) {
    return new Promise((resolve, reject) => {
      const transaction = this.db.transaction([tableName], "readwrite");
      const table = transaction.objectStore(tableName);
      const result = table.add(data);

      result.onsuccess = (event) => {
        resolve(
          this.getMessage(
            LocalDB.EVENT_TYPES.ON_INSTER_SUCCESS,
            event.target.result
          )
        );
      };
      result.onerror = (event) => {
        reject(
          this.getMessage(
            LocalDB.EVENT_TYPES.ON_INSTER_ERROR,
            `插入数据时发生错误: ${event.target.errorCode}`
          )
        );
      };
    });
  }

  /**
   * 检查表是否存在
   * @param {*} tableName
   * @returns
   */
  hasTable(tableName) {
    return this.db.objectStoreNames.contains(tableName);
  }

  getMessage(type, body) {
    return {
      dbName: this.dbName,
      version: this.version,
      type,
      body,
    };
  }
}

export default LocalDB;
